Add a script to convert libglade files to something gtkbuilder can parse.
authorJohan Dahlin <johan@src.gnome.org>
Tue, 26 Jun 2007 02:42:41 +0000 (02:42 +0000)
committerJohan Dahlin <johan@src.gnome.org>
Tue, 26 Jun 2007 02:42:41 +0000 (02:42 +0000)
    * gtk/gtk-builder-convert: Add a script to convert libglade files
    to something gtkbuilder can parse.
    Fixes #447995

svn path=/trunk/; revision=18232

ChangeLog
gtk/Makefile.am
gtk/gtk-builder-convert [new file with mode: 0644]

index d587b2956a2e1b5968dc282b06f9393421109745..3b55d92b49cc61d165c777354c4f201ed60995b9 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2007-06-25  Johan Dahlin  <jdahlin@async.com.br>
+
+       * gtk/gtk-builder-convert: Add a script to convert libglade files
+       to something gtkbuilder can parse.
+       Fixes #447995
+
 2007-06-25  Richard Hult  <richard@imendio.com>
 
        * gdk/quartz/gdkevents-quartz.c (gdk_event_translate): Don't steal
index b2d4257445cfd0330eb90e8f678dd0f615938f05..67ea241ed3ccdc64edcd78421a7bfa5cdaa7c62d 100644 (file)
@@ -888,7 +888,10 @@ LDADDS =                                                           \
 #
 # Installed tools
 #
-bin_PROGRAMS = gtk-query-immodules-2.0 gtk-update-icon-cache
+bin_PROGRAMS = \
+       gtk-query-immodules-2.0 \
+       gtk-update-icon-cache
+bin_SCRIPTS = gtk-builder-convert
 
 gtk_query_immodules_2_0_DEPENDENCIES = $(DEPS)
 gtk_query_immodules_2_0_LDADD = $(LDADDS)
diff --git a/gtk/gtk-builder-convert b/gtk/gtk-builder-convert
new file mode 100644 (file)
index 0000000..9b3ad75
--- /dev/null
@@ -0,0 +1,358 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2006-2007 Async Open Source
+#                         Henrique Romano <henrique@async.com.br>
+#                         Johan Dahlin <jdahlin@async.com.br>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# This is a script that can convert libglade files to the new gtkbuilder format
+#
+# TODO:
+#  GtkComboBox.items -> GtkListStore
+#  GtkTextView.text -> GtkTextBuffer
+#  Toolbars
+
+import sys
+
+from xml.dom import minidom, Node
+
+def get_child_nodes(node):
+    nodes = []
+    for child in node.childNodes:
+        if child.nodeType == Node.TEXT_NODE:
+            continue
+        if child.tagName != 'child':
+            continue
+        nodes.append(child)
+    return nodes
+
+def get_object_properties(node):
+    properties = {}
+    for child in node.childNodes:
+        if child.nodeType == Node.TEXT_NODE:
+            continue
+        if child.tagName != 'property':
+            continue
+        value = child.childNodes[0].data
+        properties[child.getAttribute('name')] = value
+    return properties
+
+def get_object_node(child_node):
+    assert child_node.tagName == 'child'
+    nodes = []
+    for node in child_node.childNodes:
+        if node.nodeType == Node.TEXT_NODE:
+            continue
+        if node.tagName == 'object':
+            nodes.append(node)
+    assert len(nodes) == 1, nodes
+    return nodes[0]
+
+class GtkBuilderConverter(object):
+
+    #
+    # Public API
+    #
+
+    def parse_file(self, file):
+        self._dom = minidom.parse(file)
+        self._parse()
+
+    def parse_buffer(self, buffer):
+        self._dom = minidom.parseString(buffer)
+        self._parse()
+
+    def to_xml(self):
+        return self._dom.toprettyxml("", "")
+
+    #
+    # Private
+    #
+
+    def _get_widget(self, doc, name):
+        result = self._get_widgets_by_attr(doc, "id", name)
+        if len(result) > 1:
+            raise ValueError(
+                "It is not possible to have more than one "
+                "widget with the same id (`%s')" % name)
+        elif len(result) == 1:
+            return result[0]
+        return None
+
+    def _get_widgets_by_attr(self, doc, attribute, value):
+        return [w for w in self._dom.getElementsByTagName("object")
+                      if w.getAttribute(attribute) == value]
+
+    def _create_object(self, obj_class, obj_id, **properties):
+        obj = self._dom.createElement('object')
+        obj.setAttribute('class', obj_class)
+        obj.setAttribute('id', obj_id)
+        for name, value in properties.items():
+            prop = self._dom.createElement('property')
+            prop.setAttribute('name', name)
+            prop.appendChild(self._dom.createTextNode(value))
+            obj.appendChild(prop)
+        return obj
+
+    def _get_property(self, node, property_name):
+        properties = get_object_properties(node)
+        return properties.get(property_name)
+
+    def _parse(self):
+        glade_iface = self._dom.getElementsByTagName("glade-interface")
+        assert glade_iface, ("Badly formed XML, there is "
+                             "no <glade-interface> tag.")
+        glade_iface[0].tagName = 'interface'
+        self._interface = glade_iface[0]
+
+        # Strip requires
+        requires = self._dom.getElementsByTagName("requires")
+        for require in requires:
+            require.parentNode.childNodes.remove(require)
+
+        for child in self._dom.getElementsByTagName("accessibility"):
+            child.parentNode.removeChild(child)
+
+        for node in self._dom.getElementsByTagName("widget"):
+            node.tagName = "object"
+
+        for node in self._dom.getElementsByTagName("object"):
+            self._convert(node.getAttribute("class"), node)
+
+        # Convert Gazpachos UI tag
+        for node in self._dom.getElementsByTagName("ui"):
+            self._convert_ui(node)
+
+    def _convert(self, klass, node):
+        if klass == 'GtkNotebook':
+            self._packing_prop_to_child_attr(node, "type", "tab")
+        elif klass in ['GtkExpander', 'GtkFrame']:
+            self._packing_prop_to_child_attr(
+                node, "type", "label_item", "label")
+        elif klass == "GtkMenuBar":
+            uimgr = node.ownerDocument.createElement('object')
+            uimgr.setAttribute('class', 'GtkUIManager')
+            uimgr.setAttribute('id', 'uimanager1')
+            self._interface.childNodes.insert(0, uimgr)
+            self._convert_menubar(uimgr, node)
+
+        self._default_widget_converter(node)
+
+    def _default_widget_converter(self, node):
+        klass = node.getAttribute("class")
+        for prop in node.getElementsByTagName("property"):
+            if prop.parentNode is not node:
+                continue
+            prop_name = prop.getAttribute("name")
+            if prop_name == "sizegroup":
+                self._convert_sizegroup(node, prop)
+            elif prop_name == "tooltip" and klass != "GtkAction":
+                prop.setAttribute("name", "tooltip-text")
+            elif prop_name in ["response_id", 'response-id']:
+                object_id = node.getAttribute('id')
+                response = prop.childNodes[0].data
+                self._convert_dialog_response(node, object_id, response)
+                prop.parentNode.removeChild(prop)
+
+    def _convert_menubar(self, uimgr, node):
+        menubar = self._dom.createElement('menubar')
+        menubar.setAttribute('name', node.getAttribute('id'))
+        node.setAttribute('constructor', uimgr.getAttribute('id'))
+
+        for child in get_child_nodes(node):
+            obj_node = get_object_node(child)
+            self._convert_menuitem(uimgr, menubar, obj_node)
+            child.removeChild(obj_node)
+            child.parentNode.removeChild(child)
+
+        ui = self._dom.createElement('ui')
+        uimgr.appendChild(ui)
+
+        ui.appendChild(menubar)
+
+    def _convert_menuitem(self, uimgr, menubar, obj_node):
+        children = get_child_nodes(obj_node)
+        name = 'menuitem'
+        if children:
+            child_node = children[0]
+            menu_node = get_object_node(child_node)
+            # Can be GtkImage, which will take care of later.
+            if menu_node.getAttribute('class') == 'GtkMenu':
+                name = 'menu'
+
+        object_class = obj_node.getAttribute('class')
+        if object_class in ['GtkMenuItem', 'GtkImageMenuItem']:
+            menu = self._dom.createElement(name)
+        elif object_class == 'GtkSeparatorMenuItem':
+            menu = self._dom.createElement('sep')
+        else:
+            raise NotImplementedError(object_class)
+        menu.setAttribute('name', obj_node.getAttribute('id'))
+        menu.setAttribute('action', obj_node.getAttribute('id'))
+        menubar.appendChild(menu)
+        self._add_action_from_menuitem(uimgr, obj_node)
+        if children:
+            for child in get_child_nodes(menu_node):
+                obj_node = get_object_node(child)
+                self._convert_menuitem(uimgr, menu, obj_node)
+                child.removeChild(obj_node)
+                child.parentNode.removeChild(child)
+
+    def _add_action_from_menuitem(self, uimgr, node):
+        properties = {}
+        object_class = node.getAttribute('class')
+        object_id = node.getAttribute('id')
+        if object_class == 'GtkImageMenuItem':
+            name = 'GtkAction'
+            children = get_child_nodes(node)
+            if (children and
+                children[0].getAttribute('internal-child') == 'image'):
+                image = get_object_node(children[0])
+                properties['stock_id'] = self._get_property(image, 'stock')
+        elif object_class == 'GtkMenuItem':
+            name = 'GtkAction'
+            label = self._get_property(node, 'label')
+            if label is not None:
+                properties['label'] = label
+        elif object_class == 'GtkSeparatorMenuItem':
+            return
+        else:
+            raise NotImplementedError(object_class)
+
+        if self._get_property(node, 'use_stock') == 'True':
+            properties['stock_id'] = self._get_property(node, 'label')
+        properties['name'] = object_id
+        action = self._create_object(name,
+                                     object_id,
+                                     **properties)
+        if not uimgr.childNodes:
+            child = self._dom.createElement('child')
+            uimgr.appendChild(child)
+
+            group = self._create_object('GtkActionGroup', 'actiongroup1')
+            child.appendChild(group)
+        else:
+            group = uimgr.childNodes[0].childNodes[0]
+
+        child = self._dom.createElement('child')
+        group.appendChild(child)
+        child.appendChild(action)
+
+    def _convert_sizegroup(self, node, prop):
+        # This is Gazpacho only
+        node.removeChild(prop)
+        obj = self._get_widget(doc, prop.childNodes[0].data)
+        if obj is None:
+            widgets = self._get_widgets_by_attr(doc, "class", "GtkSizeGroup")
+            if widgets:
+                obj = widgets[-1]
+            else:
+                obj = self._dom.createElement("object")
+                obj.setAttribute("class", "GtkSizeGroup")
+                obj.setAttribute("id", "sizegroup1")
+                self._interface.insertBefore(
+                    obj, self._interface.childNodes[0])
+
+        widgets = obj.getElementsByTagName("widgets")
+        if widgets:
+            assert len(widgets) == 1
+            widgets = widgets[0]
+        else:
+            widgets = self._dom.createElement("widgets")
+            obj.appendChild(widgets)
+
+        member = self._dom.createElement("widget")
+        member.setAttribute("name", node.getAttribute("id"))
+        widgets.appendChild(member)
+
+    def _convert_dialog_response(self, node, object_name, response):
+        # 1) Get parent dialog node
+        while True:
+            if (node.tagName == 'object' and
+                node.getAttribute('class') == 'GtkDialog'):
+                dialog = node
+                break
+            node = node.parentNode
+            assert node
+
+        # 2) Get dialogs action-widgets tag, create if not found
+        for child in dialog.childNodes:
+            if child.nodeType == Node.TEXT_NODE:
+                continue
+            if child.tagName == 'action-widgets':
+                actions = child
+                break
+        else:
+            actions = self._dom.createElement("action-widgets")
+            dialog.appendChild(actions)
+
+        # 3) Add action-widget tag for the response
+        action = self._dom.createElement("action-widget")
+        action.setAttribute("response", response)
+        action.appendChild(self._dom.createTextNode(object_name))
+        actions.appendChild(action)
+
+    def _packing_prop_to_child_attr(self, node, prop_name, prop_val,
+                                   attr_val=None):
+        for child in node.getElementsByTagName("child"):
+            packing_props = [p for p in child.childNodes if p.nodeName == "packing"]
+            if not packing_props:
+                continue
+            assert len(packing_props) == 1
+            packing_prop = packing_props[0]
+            properties = packing_prop.getElementsByTagName("property")
+            for prop in properties:
+                if (prop.getAttribute("name") != prop_name or
+                    prop.childNodes[0].data != prop_val):
+                    continue
+                packing_prop.removeChild(prop)
+                child.setAttribute(prop_name, attr_val or prop_val)
+            if len(properties) == 1:
+                child.removeChild(packing_prop)
+
+    def _convert_ui(self, node):
+        cdata = node.childNodes[0]
+        data = cdata.toxml().strip()
+        if not data.startswith("<![CDATA[") or not data.endswith("]]>"):
+            return
+        data = data[9:-3]
+        child = minidom.parseString(data).childNodes[0]
+        nodes = child.childNodes[:]
+        for child_node in nodes:
+            node.appendChild(child_node)
+        node.removeChild(cdata)
+        if not node.hasAttribute("id"):
+            return
+
+        # Updating references made by widgets
+        parent_id = node.parentNode.getAttribute("id")
+        for widget in self._get_widgets_by_attr(node.ownerDocument,
+                                                "constructor",
+                                                node.getAttribute("id")):
+            widget.getAttributeNode("constructor").value = parent_id
+        node.removeAttribute("id")
+
+
+def main():
+    if len(sys.argv) < 2:
+        print "Usage: %s filename.glade" % (sys.argv[0],)
+        return
+    conv = GtkBuilderConverter()
+    conv.parse_file(sys.argv[1])
+    print conv.to_xml()
+
+if __name__ == "__main__":
+    main()